/**
 ******************************************************************************
 * @file	fpga-20c.c
 * @brief	fpga-20c 存储器通信

采用原始 fpga-20c SPI通信协议

bug
    fpga 自身返回序列有缺陷，普通的控制命令容易错乱，主控端需要多次重复读取，
    返回OTDR数据是否有错误无法得知，
 *
 @section Platform
    -#
 @section Library
    -#
 *
 * @section Depend
 *	-# spi_mxs.ko
 *	-# spidev.ko

- 2016-10-21,MenglongWu,MenglongWoo@aliyun.com
*/


#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>

#include <assert.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>


#include "proginfo.h"
#include "fpga-20c.h"

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
static unsigned int fpga_alive(struct fpga_dev *dev);
static const char  *dev_desc = "fpga-20c";

#define OTDR_TEST          0x66  // OTDR 测试
#define OTDR_REDLASER      0x09  // 红光控制 没有返回值
#define OTDR_LCD_BL        0x0a  // 调节屏幕亮度 0x0a 0x01 反复无常
#define OTDR_POWER         0x0C  // 仪表关机或低功耗 1 关机; 0 低功耗 返回0
#define OTDR_CHARGE_STATUS 0x0b  // 充电状态  返回0x30 0x0b 反复无常
#define OTDR_COM_LASER     0x0D  // 通信光检测
#define OTDR_MEM_R         0x0e  // 存储器读
#define OTDR_MEM_W         0x0F  // 存储器写
#define OTDR_ALIVE         0x10  // 设备是否挂在SPI总线


#define RETRY              (12)

struct fpga_table
{
    char          pid;
    char	 *desc;
    unsigned long num_sector;
    unsigned long page_per_sector;
    unsigned long byte_per_page;
    unsigned long sector_sz;
};


// static const struct fpga_table table[] = {
// };


int fpga_close(struct fpga_dev *dev)
{
    assert(dev != NULL);
    close(dev->fd);
    free(dev);
    dev = NULL;
    return 0;
}


int fpga_open(char *device, struct fpga_dev **dev, unsigned long ispeed)
{
    // todo spinlock
    unsigned char mode  = 0;
    unsigned char bits  = 8;
    unsigned long speed = 12 * 1000 * 1000;  // 12MHz
    // unsigned short delay;
    int fd;

    if (ispeed) {
	speed = ispeed;
    }

    fd = open(device, O_RDWR);
    if (fd == 0) {
	return -1;
    }


    ioctl(fd, SPI_IOC_WR_MODE, &mode);
    ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
    ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);

    struct fpga_dev *pdev;
    pdev = (struct fpga_dev *)malloc(sizeof(struct fpga_dev));
    if (pdev == NULL) {
	goto _fail1;
    }
    bzero(pdev, sizeof(struct fpga_dev));
    // todo spinlock
    pdev->fd = fd;
    if (-1 == fpga_alive(pdev)) {
	goto _fail2;
    }
    *dev = pdev;
    return 0;
_fail1:
    close(fd);
    return -1;
_fail2:
    fpga_close(pdev);
    return -2;
}

unsigned int fpga_request_test(struct fpga_dev *dev, struct fpga_test_param *val)
{
    assert(dev != NULL);
    assert(dev->desc == dev_desc);
    uint8_t                 tx[] = { 'O', 'D', 'M', 'S', OTDR_TEST, val->laser & 0x00ff, val->laser >> 8, 0x01, val->pulse & 0x00ff, val->pulse >> 8, 0x02, val->freq & 0x00ff, val->freq >> 8, 0x03, val->adopt_pt & 0x00ff, val->adopt_pt >> 8, 0x04, val->add_cnt & 0x00ff, val->add_cnt >> 8, 0x05, val->amp, 0x06, val->apd_vol, 0x07, val->power, 0x08, val->isrun, 0x00 };
    uint8_t                 rx[1];
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf    = (unsigned long)tx,
		.rx_buf    = (unsigned long)NULL,
		.len       = ARRAY_SIZE(tx),
		.cs_change = 1,
		// .delay_usecs = 1000000,
	},
	{
		.tx_buf    = (unsigned long)NULL,
		.rx_buf    = (unsigned long)rx,
		.len       = ARRAY_SIZE(rx),
		.cs_change = 0,
		// .delay_usecs = 1000000,
	},
    };

    for (int i = 0; i < RETRY; i++) {
	ioctl(dev->fd, SPI_IOC_MESSAGE(2), &arr);
	if (rx[0] == OTDR_TEST) {
	    return 0;
	}
    }
    return -1;
}

unsigned int fpga_red_laser(struct fpga_dev *dev, int val)
{
    assert(dev != NULL);
    assert(dev->desc == dev_desc);
    assert(val == OTDR_RL_OFF || val == OTDR_RL_ON || val == OTDR_RL_1HZ || val == OTDR_RL_10HZ);
    uint8_t                 tx[]  = { 'O', 'D', 'M', 'S', OTDR_REDLASER, val };  // 红光控制 没有返回值
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf    = (unsigned long)tx,
		.rx_buf    = (unsigned long)NULL,
		.len       = ARRAY_SIZE(tx),
		.cs_change = 1,
	},
    };


    ioctl(dev->fd, SPI_IOC_MESSAGE(1), &arr);
    return 0;
}

/**
 * @brief	探测FPGA是否挂接到总线上
 * @param[in]	dev 设备描述符
 * @retval	0 找到设备
 * @retval	-1 没找到设备
 */
static unsigned int fpga_alive(struct fpga_dev *dev)
{
    uint8_t                 tx[] = { 'O', 'D', 'M', 'S', OTDR_ALIVE };  // spi测试  返回值0x10
    uint8_t                 rx[1];
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf    = (unsigned long)tx,
		.rx_buf    = (unsigned long)NULL,
		.len       = ARRAY_SIZE(tx),
		.cs_change = 1,
	},
	{
		.tx_buf    = (unsigned long)NULL,
		.rx_buf    = (unsigned long)rx,
		.len       = ARRAY_SIZE(rx),
		.cs_change = 0,
	},
    };

    // Step 1.第一次读取，如果能立马读到就可以马上返回
    for (int i = 0; i < RETRY; i++) {
	ioctl(dev->fd, SPI_IOC_MESSAGE(2), &arr);
	if (rx[0] == OTDR_ALIVE) {
	    dev->desc = (char *)dev_desc;
	    return 0;
	}
    }

    // Step 2. fpga缺陷，当上次有OTDR数据且未被读出时，缓存的内容永远是数据
    // 反复读取内容保证吧FPGA读空
    uint8_t                 rd[4000];
    struct spi_ioc_transfer dump[] = {
	{
		.tx_buf    = (unsigned long)NULL,
		.rx_buf    = (unsigned long)rd,
		.len       = ARRAY_SIZE(rd),
		.cs_change = 0,
	},
    };
    for (int i = 0; i < 40; i++) {
	ioctl(dev->fd, SPI_IOC_MESSAGE(1), &dump);
    }


    // Step 3. 最后尝试读取
    for (int i = 0; i < RETRY; i++) {
	ioctl(dev->fd, SPI_IOC_MESSAGE(2), &arr);
	if (rx[0] == OTDR_ALIVE) {
	    dev->desc = (char *)dev_desc;
	    return 0;
	}
    }
    return -1;
}

unsigned int fpga_lcd_bl(struct fpga_dev *dev, int val)
{
    assert(dev != NULL);
    assert(dev->desc == dev_desc);
    uint8_t                 tx[] = { 'O', 'D', 'M', 'S', OTDR_LCD_BL, val };  // spi测试  返回值0x10
    uint8_t                 rx[1];
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf    = (unsigned long)tx,
		.rx_buf    = (unsigned long)NULL,
		.len       = ARRAY_SIZE(tx),
		.cs_change = 1,
	},
	{
		.tx_buf    = (unsigned long)NULL,
		.rx_buf    = (unsigned long)rx,
		.len       = ARRAY_SIZE(rx),
		.cs_change = 0,
	},
    };

    for (int i = 0; i < RETRY; i++) {
	ioctl(dev->fd, SPI_IOC_MESSAGE(2), &arr);
	if (rx[0] == OTDR_LCD_BL) {
	    return 0;
	}
    }
    return -1;
}

unsigned int fpga_charge_status(struct fpga_dev *dev, char *val)
{
    assert(dev != NULL);
    assert(dev->desc == dev_desc);
    uint8_t                 tx[] = { 'O', 'D', 'M', 'S', OTDR_CHARGE_STATUS };  // spi测试  返回值0x10
    uint8_t                 rx[4];
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf    = (unsigned long)tx,
		.rx_buf    = (unsigned long)NULL,
		.len       = ARRAY_SIZE(tx),
		.cs_change = 1,
	},
	{
		.tx_buf    = (unsigned long)NULL,
		.rx_buf    = (unsigned long)rx,
		.len       = ARRAY_SIZE(rx),
		.cs_change = 0,
	},
    };

    for (int i = 0; i < RETRY; i++) {
	ioctl(dev->fd, SPI_IOC_MESSAGE(2), &arr);
	if (rx[0] == OTDR_CHARGE_STATUS) {
	    *val = rx[1];
	    return 0;
	}
    }
    return -1;
}


unsigned int fpga_low_power(struct fpga_dev *dev, int val)
{
    assert(dev != NULL);
    assert(dev->desc == dev_desc);
    // 仪表关机或低功耗 1 关机; 0 低功耗 返回0
    // 正常功率呢？是2吗
    uint8_t                 tx[] = { 'O', 'D', 'M', 'S', OTDR_POWER, val };
    uint8_t                 rx[1];
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf    = (unsigned long)tx,
		.rx_buf    = (unsigned long)NULL,
		.len       = ARRAY_SIZE(tx),
		.cs_change = 1,
	},
	{
		.tx_buf    = (unsigned long)NULL,
		.rx_buf    = (unsigned long)rx,
		.len       = ARRAY_SIZE(rx),
		.cs_change = 0,
	},
    };
    ioctl(dev->fd, SPI_IOC_MESSAGE(2), &arr);
    return 0;
}

unsigned int fpga_com_laser(struct fpga_dev *dev, int *val)
{
    assert(dev != NULL);
    assert(dev->desc == dev_desc);

    // 仪表关机或低功耗 1 关机; 0 低功耗 返回0
    // 正常功率呢？是2吗
    uint8_t                 tx[] = { 'O', 'D', 'M', 'S', OTDR_COM_LASER };
    uint8_t                 rx[2];
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf    = (unsigned long)tx,
		.rx_buf    = (unsigned long)NULL,
		.len       = ARRAY_SIZE(tx),
		.cs_change = 1,
	},
	{
		.tx_buf    = (unsigned long)NULL,
		.rx_buf    = (unsigned long)rx,
		.len       = ARRAY_SIZE(rx),
		.cs_change = 0,
	},
    };

    for (int i = 0; i < RETRY; i++) {
	ioctl(dev->fd, SPI_IOC_MESSAGE(2), &arr);
	if (rx[0] == OTDR_COM_LASER) {
	    // FPGA通信错误
	    assert(rx[1] == COM_LASER || rx[1] == COM_FREE);
	    *val = rx[1];

	    return 0;
	}
    }
    return -1;
}


unsigned int fpga_otdr(struct fpga_dev *dev)
{
    // uint8_t tx[] = {'O', 'D', 'M', 'S', 0x09, 1}; // 红光控制 没有返回值
    // uint8_t tx[] = {'O','D','M','S',0x0a,1};// 调节屏幕亮度 0x0a 0x01 反复无常
    // uint8_t tx[] = {'O','D','M','S',0x0C,0};// 仪表关机或低功耗 1 关机; 0 低功耗 返回0

    // 发6byte 读2byte 返回 <cmd> <val>
    uint8_t tx[] = { 'O', 'D', 'M', 'S', 0x0b };  // 充电状态  返回0x30 0x0b 反复无常

    // 发6byte 读2byte 返回 <cmd> <val> val 有光 1 没光2
    // uint8_t tx[] = {'O','D','M','S',0x0D,1};// 通信光检测
    // uint8_t tx[] = {'O','D','M','S',0x0e,1};// 存储器读
    // uint8_t tx[] = {'O','D','M','S',0x0F,1};// 存储器写

    // 发6byte 读1byte 返回 <cmd> <val>
    // uint8_t tx[] = {'O', 'D', 'M', 'S', 0x10}; // spi测试  返回值0x10
    uint8_t                 rx[4];
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf    = (unsigned long)tx,
		.rx_buf    = (unsigned long)NULL,
		.len       = ARRAY_SIZE(tx),
		.cs_change = 1,
	},
	{
		.tx_buf    = (unsigned long)NULL,
		.rx_buf    = (unsigned long)rx,
		.len       = ARRAY_SIZE(rx),
		.cs_change = 0,
	},
    };
    int ret;

    ret = ioctl(dev->fd, SPI_IOC_MESSAGE(2), &arr);
    return ret;
}

/*
 * 数据组成格式 以下均为16进制
 * 66 23 aa bb cc 23 aa bb cc 23 aa bb cc
 * 其中 66 表示数据已经准备完毕
 * 23 表示数据分割符
 * aa bb cc 是累加若干次的结果，具体累加次数由 fpga_request_test 提供
 * 转换成24byte后小端处理器需要将其转换字节序
 * 0xccbbaa
 */
unsigned int fpga_read_ad(struct fpga_dev *dev, unsigned long *pt, unsigned long len)
{
    assert(dev != NULL);
    assert(dev->desc == dev_desc);
    assert(pt != NULL);
    assert(len == 32000 || len == 64000);  // 只允许采样点数

    uint8_t                 rx[4000];
    struct spi_ioc_transfer arr[] = {
	{
		.tx_buf    = (unsigned long)NULL,
		.rx_buf    = (unsigned long)rx,
		.len       = 1,
		.cs_change = 0,
	},
    };
    int  i = 0;
    long data;
    // long *pdata = &data;
    char *pch     = (char *)&data;
    long  lenbyte = len * 4, rxlen = 0;


    // 等待数据采集完成
    ioctl(dev->fd, SPI_IOC_MESSAGE(1), &arr);
    if (rx[0] != '#') {
	return -2;
    }
    // 跳过第一个点
    arr[0].len = 3;
    ioctl(dev->fd, SPI_IOC_MESSAGE(1), &arr);
    // pch = pt;
    // pch[0] = rx[0];
    // pch[1] = rx[1];
    // pch[2] = rx[2];
    // pch[3] = 0x23;


    // 提取数据
    pch = (char *)(pt + 1);
    lenbyte -= 4;
    while (lenbyte > 0) {
	if (ARRAY_SIZE(rx) < lenbyte) {
	    arr[0].len = ARRAY_SIZE(rx);
	    rxlen      = ARRAY_SIZE(rx);
	}
	else {
	    arr[0].len = ARRAY_SIZE(rx);
	    rxlen      = lenbyte;
	}
	lenbyte -= rxlen;
	ioctl(dev->fd, SPI_IOC_MESSAGE(1), &arr);
	i = 0;
	while (i < rxlen) {
	    // OTDR数据被其他数据覆盖
	    // assert(rx[i + 0] == 0x23);
	    if (rx[i + 0] == 0x23) {
		pch[i + 0] = rx[i + 3];
		pch[i + 1] = rx[i + 2];
		pch[i + 2] = rx[i + 1];
		pch[i + 3] = rx[i + 0];
	    }

	    i += 4;
	}
	pch += rxlen;
    }
    // 第一个点数值异常
    pt[0] = pt[1];
    return 0;
}


#ifdef PRJ_NAME
MODULE_PROJECT(PRJ_NAME);
#endif
#if defined(PRJ_VERSION) && defined(PRJ_PATCHLEVEL) && defined(PRJ_SUBLEVEL)
MODULE_VERSION(PRJ_VERSION "." PRJ_PATCHLEVEL "." PRJ_SUBLEVEL);
#endif

#ifdef BUILD_DATE
MODULE_BUILD(BUILD_DATE);
#endif

MODULE_AUTHOR("MenglongWu <MenglongWoo@aliyun.com>");
MODULE_DEPEND("spi_mxs.ko spidev.ko");
MODULE_DESCRIPTION("The GLink OTDR FPGA communicate interface");
